Java 自定义注解的学习笔记

这篇博文主要是我在学习 Java Annotation 时的笔记和感悟。学习 Java web 开发会发现,现在越来越多的 Web 开发框架慢慢舍弃不必要的传统的 XML、properties 等配置文件,使用 Annotation 的方式来配置框架程序运行时所需的信息和配置。这种比较轻便的注解配置的方式也使得程序开发更加快捷,更利于程序维护,至少当程序中 Java 代码更改之后,不需要在另外一个地方的 XML 修改配置。

Java 语言中的 Annotation 为何物?

Java 在 1.5 版本开始以特殊的元数据(关于数据信息的数据)的形式引入注解(Annotation)。那么究竟什么是 Java 中的 Annotation 呢? 看下它的定义:

Annotation 是 Java 语言中的一种特殊的元数据语法,可以被添加到 Java 代码中。类,方法,变量,参数,包都可以被标注。与 Javadoc 的标签不同,注解是可以被反射的,因为他们被编译器生成嵌入在编译后文件,并保留在虚拟机中以便在运行时被索引。

Annotation 是与一个程序元素关联信息或者元数据的标注。它不影响 Java 程序的执行,但是对例如编译器警告或者像文档生成器等辅助工具产生影响。

从定义中可以了解到,Java Annotation 是一种用来标注数据或者程序语言元素信息的“工具”,它不会改变程序的执行过程,但是可以利用相关的工具根据 Annotation 的具体定义和内容对它标注的数据进行一些操作。

Java 中内建的注解(Built-in Annotation)和自定义注解

在使用 Java 的过程中,我们会碰到的内建注解不多,比较常见的是 @Override @Deprecated @SuppressWarnings 这三个,具体作用不再详细叙述。

主要说一下 Java 自定义的注解。

怎么定义一个注解

下面是一个自定义注解的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ValueBind {
String value() default "";
}

从这个例子中可以看到:

  1. 自定义注解的类型为 @interface

  2. 自定义注解可以被元注解标记,比如 @Document

  3. 自定义注解可以有“方法属性”,并且可以提供一个默认值作为返回值

元注解

上面例子展示了定义自己的注解的时候可以使用的元注解,包括 @Documented , @Target , @Retention@Inherited 这四个。下面是这四个元注解的源代码和他们的作用。

  • @Document

源代码和用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package java.lang.annotation;
/**
* Indicates that annotations with a type are to be documented by javadoc
* and similar tools by default. This type should be used to annotate the
* declarations of types whose annotations affect the use of annotated
* elements by their clients. If a type declaration is annotated with
* Documented, its annotations become part of the public API
* of the annotated elements.
*中文:
*这个被这个注解标明的 Type 默认将会被 javadoc 和相似的工具记录。
*这个注解应该用在声明那些可能影响它标注对象的使用的注解身上。
*如果一个 type
*声明的时候被用这个注解标注了,那么他的注解也会成为公共 API 的一部分。
*
* @author Joshua Bloch
* @version 1.6, 11/17/05
* @since 1.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
  • @Target

他的源码和用法:

1
2
3
4
5
6
7
8
9
/**
*指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target *元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译*器强制实施指定的使用限制。
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

其中可以使用的 Vlaue 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE
}
  • @Retention
    源码和用法:
1
2
3
4
5
6
7
8
9
10
11
12
/**
*指示注释类型的注解要保留多久。如果注解的声明中不存在 Retention
*注释,则保留策略默认为 RetentionPolicy.CLASS。
*只有元注释类型直接用于注释时,Target
*元注释才有效。如果元注释类型用作另一种注释类型的成员,则无效。
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}

可以使用的 Value 和对应的意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* 编译器忽略的注解
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time.
* This is the default
* behavior.
* 编译器可以识别,但是运行时 Jvm 忽略的注解
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler
* and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
* 编译器、JVM 都可以使用的注解类型,可以通过反射找到并使用
*/
RUNTIME
}
  • @Inherited
    源码和使用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
*指示注释类型被自动继承。
*如果在注释类型声明中存在 Inherited 元注释,
*并且用户在某一类声明中查询该注释类型,
*同时该类声明中没有此类型的注释,则将在该类的超类中自动查询该注释类型。
*此过程会重复进行,直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止。
*如果没有超类具有该类型的注释,则查询将指示当前类没有这样的注释。
*注意,如果使用注释类型注释 Class 以外的任何事物,
*此元注释类型都是无效的。
*还要注意,此元注释仅促成从超类继承注释,对实现接口的方式该注释无效。
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

自定义注解的使用

注解如果没有被解析和使用,和标记在对应位置的注释是一样的。
那么如何解析注解呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package annotation;
@Model
public class Student {
true
true@ValueBinding("Tom Hanks")
truepublic String name;
true
true@ValueBinding("21")
truepublic String age;
true
true@ValueBinding
truepublic String gender;
}
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Model {
}
package annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValueBinding {
truepublic String value() default "man";
}
package annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
public class AnnotationParse {
truepublic static void parseModel(Class clazz) {
true //获取到注解
truetrueAnnotation anno = clazz.getAnnotation(Model.class);
truetrueif (anno != null) {
truetrue //do something
truetruetrueSystem.out.print("create table ");
truetruetrueSystem.out.println(clazz.getName().replace(".", "_"));
truetrue}
true}
truepublic static Object praseValueBinding(Class clazz)
truetruetruethrows InstantiationException, IllegalAccessException {
truetrueObject obj = clazz.newInstance();
//获取到 field ,然后获取注解
truetrueField[] fields = clazz.getDeclaredFields();
truetruefor (Field field : fields) {
truetruetrueValueBinding anno = field.getAnnotation(ValueBinding.class);
truetruetrueif (anno != null) {
truetruetrue //得到注解的参数值
truetruetruetrueString value = anno.value();
truetruetruetruefield.set(obj, value);
truetruetrue}
truetrue}
truetruereturn obj;
true}
}
package annotation;
public class AnnotationTest {
truepublic static void main(String[] args) {
truetruetry{
truetruetrueAnnotationParse.parseModel(Student.class);
truetruetrueStudent stu = (Student)AnnotationParse.praseValueBinding(Student.class);
truetruetrueSystem.out.println(stu.name);
truetruetrueSystem.out.println(stu.age);
truetruetrueSystem.out.println(stu.gender);
truetrue}
truetruecatch(Exception e){
truetruetrueSystem.out.println("Error happen");
truetrue}
true}
}

总结

注解的定义和使用很方便,也很简单,如何合理的设计注解成为关键性的步骤。通常需要全面考虑自己的需求,然后制定合理的注解层次。

那么 Spring 里面是怎么使用注解的呢?可以参考下它的源代码和别人的文章,这里就不在赘述。也许等我学习到 Spring 的源代码的时候会去看下。

注解的解释用到了反射技术,众所周知的是反射的效率不高,所以,我们应该把对注解的处理尽量放在初始化的时候进行,并且做好缓存。

结束。